// MapConv.cpp : Defines the entry point for the console application.
//

#include <assert.h>
#include "Bitmap.h"
#include <string>
#include <stdio.h>

#include "FileHandler.h"
#include "FeatureCreator.h"
#include "TileHandler.h"

extern "C"
{
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}

#include "squish.h"

#include "mapfile.h"

#define VERSION "0.3"

using namespace std;

CFeatureCreator featureCreator;

void ConvertTextures(string intexname,string temptexname,int xsize,int ysize);
void LoadHeightMap(string inname,int xsize,int ysize,float minHeight,float maxHeight,bool invert,bool lowpass);
void SaveHeightMap(ofstream& outfile,int xsize,int ysize,float minHeight,float maxHeight);
void SaveTexOffsets(ofstream &outfile,string temptexname,int xsize,int ysize);
void SaveTextures(ofstream &outfile,string temptexname,int xsize,int ysize);

void SaveMiniMap(ofstream &outfile);
void SaveMetalMap(ofstream &outfile, std::string metalmap, int xsize, int ysize);
void SaveTypeMap(ofstream &outfile,int xsize,int ysize,string typemap);

float* heightmap;

lua_State * lua;

const char * LuaGetTableString(char *table, char *key)
{
    const char * result;
    lua_getglobal(lua, table);
    if (!lua_istable(lua, -1)) {
        printf("%s is not a table", table);
    }
    lua_pushstring(lua, key);
    lua_gettable(lua, -2);
    if(!lua_isstring(lua, -1)) {
        printf("%s is not a string", key);
    }
    result = (char *)lua_tostring(lua, -1);
    lua_pop(lua, 1);
    return result;
}

int LuaGetTableInt(char *table, char *key)
{
    int result;
    lua_getglobal(lua, table);
    if (!lua_istable(lua, -1)) {
        printf("%s is not a table", table);
    }
    lua_pushstring(lua, key);
    lua_gettable(lua, -2);
    if(!lua_isnumber(lua, -1)) {
        printf("%s is not a number", key);
    }
    result = lua_tonumber(lua, -1);
    lua_pop(lua, 1);
    return result;
}

int LuaGetTableBool(char *table, char *key)
{
    int result;
    lua_getglobal(lua, table);
    if (!lua_istable(lua, -1)) {
        printf("%s is not a table", table);
    }
    lua_pushstring(lua, key);
    lua_gettable(lua, -2);
    if(!lua_isboolean(lua, -1)) {
        printf("%s is not a boolean", key);
    }
    result = lua_toboolean(lua, -1);
    lua_pop(lua, 1);
    return result;
}

float LuaGetTableFloat(char *table, char *key)
{
    float result;
    lua_getglobal(lua, table);
    if (!lua_istable(lua, -1)) {
        printf("%s is not a table", table);
    }
    lua_pushstring(lua, key);
    lua_gettable(lua, -2);
    if(!lua_isnumber(lua, -1)) {
        printf("%s is not a float", key);
    }
    result = lua_tonumber(lua, -1);
    lua_pop(lua, 1);
    return result;
}

int LuaGetInt(char *key)
{
    int result;
    lua_getglobal(lua, key);
    result = lua_tointeger(lua, -1);
    lua_pop(lua, 1);
    return result;
}

float LuaGetFloat(char *key)
{
    float result;
    lua_getglobal(lua, key);
    result = lua_tonumber(lua, -1);
    lua_pop(lua, 1);
    return result;
}

int LuaGetBool(char *key)
{
    int result;
    lua_getglobal(lua, key);
    result = lua_toboolean(lua, -1);
    lua_pop(lua, 1);
    return result;
}

int main(int argc, char* argv[])
{
	int xsize;
	int ysize;
	string intexname="mars.bmp";
	string inHeightName="mars.raw";
	string outfilename="mars.smf";
	string metalmap="marsmetal.bmp";
	string typemap="";
	string extTileFile="";
	string featuremap="";
	string geoVentFile="geovent.bmp";
	string featureListFile="fs.txt";

	float minHeight=20;
	float maxHeight=300;
	float compressFactor=0.8f;
	float whereisit=0;
	bool invertHeightMap=false;
	bool lowpassFilter=false;
	vector<string> F_Spec;

    if (argc != 2) {
            printf("Usage: %s <mapfile>\n", argv[0]);
            printf("  This is version " VERSION "\n");
            return -1;
    }

    lua = luaL_newstate();
    luaL_openlibs(lua);

    if (luaL_dofile(lua, argv[1]) != 0) {
        printf("%s", lua_tostring(lua, -1));
        lua_close(lua);
        return -1;
    }

    outfilename.assign((const char *)LuaGetTableString("Map", "name"));
    outfilename.append(".smf");
    inHeightName.assign(LuaGetTableString("Map", "height_map"));
    intexname.assign(LuaGetTableString("Map", "texture_map"));
    metalmap.assign(LuaGetTableString("Map", "metal_map"));
    typemap.assign(LuaGetTableString("Map", "type_map"));
    featuremap.assign(LuaGetTableString("Map", "feature_map"));

    minHeight = LuaGetTableInt("Map", "min_height");
    maxHeight = LuaGetTableInt("Map", "max_height");

    invertHeightMap= LuaGetTableBool("Map", "invert") == 0 ? false : true;
    lowpassFilter = LuaGetTableBool("Map", "lowpass") == 0 ? false : true;
    compressFactor = LuaGetTableFloat("Map", "compress");

	if(!extTileFile.empty())
		tileHandler.AddExternalTileFile(extTileFile);
	tileHandler.ProcessTiles(compressFactor);

	tileHandler.LoadTexture(intexname);
	tileHandler.SetOutputFile(outfilename);
	if(!extTileFile.empty())
		tileHandler.AddExternalTileFile(extTileFile);

	xsize=tileHandler.xsize;
	ysize=tileHandler.ysize;

	LoadHeightMap(inHeightName,xsize,ysize,minHeight,maxHeight,invertHeightMap,lowpassFilter);

	ifstream ifs;
	int numNamedFeatures=0;

	ifs.open(featureListFile.c_str(), ifstream::in);
	while (ifs.good()){
			char c[100]="";
			ifs.getline(c,100);
			F_Spec.push_back(c);
			numNamedFeatures++;
	}
	featureCreator.CreateFeatures(&tileHandler.bigTex,0,0,numNamedFeatures,featuremap,geoVentFile);

	tileHandler.ProcessTiles(compressFactor);

	unsigned long li;
	li = LuaGetTableInt("Map", "id");

	MapHeader header;
	strcpy(header.magic,"spring map file");
	header.version=1;
	header.mapid=li;		//todo: this should be made better to make it depend on heightmap etc, but this should be enough to make each map unique
	header.mapx=xsize;
	header.mapy=ysize;
	header.squareSize=8;
	header.texelPerSquare=8;
	header.tilesize=32;
	header.minHeight=minHeight;
	header.maxHeight=maxHeight;

	header.numExtraHeaders=1;

	int headerSize=sizeof(MapHeader);
	headerSize+=12;		//size of vegetation extra header

	header.heightmapPtr = headerSize;
	header.typeMapPtr= header.heightmapPtr + (xsize+1)*(ysize+1)*2;
	header.minimapPtr = header.typeMapPtr + (xsize/2) * (ysize/2);
	header.tilesPtr = header.minimapPtr + MINIMAP_SIZE;
	header.metalmapPtr = header.tilesPtr + tileHandler.GetFileSize();
	header.featurePtr = header.metalmapPtr + (xsize/2)*(ysize/2) + (xsize/4*ysize/4);	//last one is space for vegetation map

	ofstream outfile(outfilename.c_str(), ios::out|ios::binary);

	outfile.write((char*)&header,sizeof(MapHeader));

	int temp=12;	//extra header size
	outfile.write((char*)&temp,4);
	temp=MEH_Vegetation;	//extra header type
	outfile.write((char*)&temp,4);
	temp=header.metalmapPtr + (xsize/2)*(ysize/2);		//offset to vegetation map
	outfile.write((char*)&temp,4);

	SaveHeightMap(outfile,xsize,ysize,minHeight,maxHeight);

	SaveTypeMap(outfile,xsize,ysize,typemap);
	SaveMiniMap(outfile);

	tileHandler.ProcessTiles2();
	tileHandler.SaveData(outfile);

	SaveMetalMap(outfile, metalmap,xsize,ysize);

	featureCreator.WriteToFile(&outfile, F_Spec);

	delete[] heightmap;

	lua_close(lua);

	return 0;
}

// A 1024x1024 texture with 9 mipmap sublevels
void SaveMiniMap(ofstream &outfile)
{
	printf("Creating minimap\n");
    printf("  Squishing... ");

    int total =0;

    for (int mm = 0; mm < MINIMAP_NUM_MIPMAP; mm++) {
        CBitmap mini = tileHandler.bigTex.CreateRescaled(1024 >> mm, 1024 >> mm);
        int sz = GetStorageRequirements(mini.xsize, mini.ysize, squish::kDxt1);
        squish::u8 * blocks = new squish::u8 [sz];
        squish::CompressImage( mini.mem, mini.xsize, mini.ysize, blocks, squish::kDxt1 | squish::kColourRangeFit);
        outfile.write((char *)blocks, sz);
        delete [] blocks;
        total += sz;
        printf("%d%% ", ((mm+1)*100)/MINIMAP_NUM_MIPMAP);
    }

    if (total != MINIMAP_SIZE) {
            printf("Bad mini map size: %d (expected %d)\n", total, MINIMAP_SIZE);
    }

    assert(total == MINIMAP_SIZE);

    printf("  Squish done!\n");
}

void LoadHeightMap(string inname,int xsize,int ysize,float minHeight,float maxHeight,bool invert,bool lowpass)
{
	printf("Creating height map\n");

	float hDif=maxHeight-minHeight;
	int mapx=xsize+1;
	int mapy=ysize+1;
	heightmap=new float[mapx*mapy];

	if(inname.find(".raw")!=string::npos){		//16 bit raw
	    printf("Using raw height field\n");
		CFileHandler fh(inname);

		for(int y=0;y<mapy;++y){
			for(int x=0;x<mapx;++x){
				unsigned short h;
				fh.Read(&h,2);
				heightmap[(ysize-y)*mapx+x]=(float(h))/65535*hDif+minHeight;
			}
		}
	} else {		//standard image
		CBitmap bm(inname);
		if(bm.xsize!=mapx || bm.ysize!=mapy){
			printf("Erroneous dimensions for heightmap image (expected %dx%d)", mapx, mapy);
			exit(0);
		}
		for(int y=0;y<mapy;++y){
			for(int x=0;x<mapx;++x){
				unsigned short h=(int)bm.mem[(y*mapx+x)*4]*256;
				heightmap[(ysize-y)*mapx+x]=(float(h))/65535*hDif+minHeight;
			}
		}
	}

	if(invert){
	    printf("Inverting height map\n");
		float* heightmap2=heightmap;
		heightmap=new float[mapx*mapy];
		for(int y=0;y<mapy;++y){
			for(int x=0;x<mapx;++x){
				heightmap[y*mapx+x]=heightmap2[(mapy-y-1)*mapx+x];
			}
		}
		delete[] heightmap2;
	}

	if(lowpass){
	    printf("Lowpass height map\n");
		float* heightmap2=heightmap;
		heightmap=new float[mapx*mapy];
		for(int y=0;y<mapy;++y){
			for(int x=0;x<mapx;++x){
				float h=0;
				float tmod=0;
				for(int y2=max(0,y-2);y2<min(mapy,y+3);++y2){
					int dy=y2-y;
					for(int x2=max(0,x-2);x2<min(mapx,x+3);++x2){
						int dx=x2-x;
						float mod=max(0.0f,1.0f-0.4f*sqrtf(float(dx*dx+dy*dy)));
						tmod+=mod;
						h+=heightmap2[y2*mapx+x2]*mod;
					}
				}
				heightmap[y*mapx+x]=h/tmod;
			}
		}
		delete[] heightmap2;
	}
}

void SaveHeightMap(ofstream& outfile,int xsize,int ysize,float minHeight,float maxHeight)
{
	int mapx=xsize+1;
	int mapy=ysize+1;
	unsigned short* hm=new unsigned short[mapx*mapy];

	float sub=minHeight;
	float mul=(1.0f/(maxHeight-minHeight))*0xffff;
	for(int y=0;y<mapy;++y){
		for(int x=0;x<mapx;++x){
			hm[y*mapx+x]=(unsigned short)((heightmap[y*mapx+x]-sub)*mul);
		}
	}
	outfile.write((char*)hm,mapx*mapy*2);

	delete[] hm;
}

void SaveMetalMap(ofstream &outfile, std::string metalmap, int xsize, int ysize)
{
	printf("Saving metal map\n");

	CBitmap metal(metalmap);
	if(metal.xsize!=xsize/2 || metal.ysize!=ysize/2){
		metal=metal.CreateRescaled(xsize/2,ysize/2);
	}
	int size = (xsize/2)*(ysize/2);
	char *buf = new char[size];

	for(int y=0;y<metal.ysize;++y)
		for(int x=0;x<metal.xsize;++x)
			buf[y*metal.xsize+x]=metal.mem[(y*metal.xsize+x)*4];	//we use the red component of the picture

	outfile.write(buf, size);

	delete [] buf;
}

void SaveTypeMap(ofstream &outfile,int xsize,int ysize,string typemap)
{
	int mapx=xsize/2;
	int mapy=ysize/2;

	unsigned char* typeMapMem=new unsigned char[mapx*mapy];
	memset(typeMapMem,0,mapx*mapy);

	if(!typemap.empty()){
		CBitmap tm;
		tm.Load(typemap);
		CBitmap tm2=tm.CreateRescaled(mapx,mapy);
		for(int a=0;a<mapx*mapy;++a)
			typeMapMem[a]=tm2.mem[a*4];
	}
	outfile.write((char*)typeMapMem,mapx*mapy);

	delete[] typeMapMem;
}
